'''
Created on 08/12/2011

@author: nacho
'''

from block import Block
from memory import Memory
from allocationMethod import Allocation
from assignationData import AssignationData
from cpuInstruction import CPUInstruction
from program import Program
from assignationFirstAdjustment import FirstAdjustment
from block import Block


class Assignation(Allocation):
    '''
    classdocs
    '''


    def __init__(self,  assignationMethod, memory):
        '''
        Constructor
        '''
        
        self.assignation = assignationMethod
        self.memory = memory
        self.usedBlocks = []
        self.unusedBlocks = []
        bloque = Block(0, memory.size() - 1)
        self.unusedBlocks.append(bloque)
        self.assignationTable = {}
        
    def getUnusedBlocks(self):
        
        return self.unusedBlocks
    
    def getUsedBlocks(self):
        
        return self.usedBlocks
        
    def allocate(self, pid, program, mmu, memo):
        
        
        
        if self.isEnoughSpaceForProgram(program): # fijarse si hay al menos un bloque que quepa
            
            baseDir = self.assignation.allocateP(program, self) #que verga devuelve ??? BASE DIR
            self.assignationTable[pid] = AssignationData(baseDir, len(program))##falta agregar a la tablita de pid y base dirs
            print("Program loaded")
        
        elif self.isEnoughSpaceForProgramCompacting(program):
            self.compact()
            self.allocate(pid, program, mmu,memo) ####caso contrario que saque una cuenta para ver si compactando se puede almacenar
            print("Program loaded after compact")
        
        else:
            print("No hay lugar amigacho")
            mmu.thereIsNoSpaceInMemory(pid)

            
            
            
    
    def read(self, pid, instrNumber, memo):
        
        asData = self.assignationTable[pid] #retorna un AssignationData
        baseDir = asData.getBaseDir() #Retorna la direccion base de ese pid
        return self.memory.read(baseDir + instrNumber)
    
    def unload(self, pid, memo):
        
        asData = self.assignationTable[pid]
        baseDir = asData.getBaseDir()
        numInstructions = asData.getAmountOfInstructions()
        
        while numInstructions > 0:
            
            self.memory.unload(baseDir + numInstructions - 1)
            numInstructions -= 1  
            
        self.free(baseDir)
        del self.assignationTable[pid]
        
    def resizeBlock(self, block, end):
        
        usedBlock = block.reduceBlock(end)
        if self.isContinuous(block, self.usedBlocks):
            
            self.appendBlocks(block, self.usedBlocks)
            
        else:
            
            self.usedBlocks.append(usedBlock)
        return usedBlock.getStart() ##retorna baseDir
    
    
    def checkForContinuous(self, block, blockList):
        
        listappended = []
        if self.isContinuous(block, blockList):
            while self.isContinuous(block, blockList):
                listappended = self.appendBlocks(block, blockList)
        else:
            blockList.append(block)
            
        return listappended
            
            
            
    def appendBlocks(self, block, blockList):
        
        for b in blockList:
            
            if b.continuousBlocks(block):
                resized = block.reSize(b)
                removible = b
        
        blockList.remove(removible)
        blockList.append(resized)
        return blockList
                    
    def loadFirstBlock(self, program):
        
        hasBeenLoaded = False
        self.sortBlocks(self.getUnusedBlocks(), lambda block: block.getStart())
        
        for b in self.getUnusedBlocks():
            
            if b.blockSize() >= len(program) and not hasBeenLoaded:
               
                hasBeenLoaded = True
                return self.loadProgram(program, b) 
                        
    def loadSmallerBlock(self, program):
        
        hasBeenLoaded = False
        self.sortBlocks(self.unusedBlocks, lambda block: block.blockSize())
        for b in self.unusedBlocks:
            
            if b.blockSize() >= len(program) and not hasBeenLoaded:
               
                hasBeenLoaded = True
                return self.loadProgram(program, b) 
               
    def loadBiggerBlock(self, program):
        
        hasBeenLoaded = False
        self.sortBlocks(self.unusedBlocks, lambda block: block.blockSize())
        self.unusedBlocks.reverse()
        for b in self.unusedBlocks:
            
            if b.blockSize() >= len(program) and not hasBeenLoaded:
                hasBeenLoaded = True
                return self.loadProgram(program, b) 
            
    def sortBlocks(self, blockList, condition):
        
        blockList.sort(key = condition)
        
    def isContinuous(self, block, blockList):
        
        temp = False
        for b in blockList:
            
            if b.continuousBlocks(block):
                
                temp = True
                
        return temp
    
    
    
    def free(self, baseDir):
        
        for b in self.usedBlocks:
            
            if b.getStart() == baseDir:
                self.usedBlocks.remove(b)
                self.checkForContinuous(b, self.unusedBlocks)
             

                

    

                               
    def loadProgram(self, program, block):
        
        counter = 0
        for i in program:
           
            self.memory.load(block.getStart() + counter, i) #cargo cada instruccion en memoria
            
            counter += 1 
            ##RESIZE DE BLOQUE
        
        return self.resizeBlock(block, counter) ##termina retornando baseDir
    
    
    def allocateRegister(self, value):
        
        block = self.unusedBlocks[0]
        dirE = block.registerAllocation()
        self.memory.load(dirE, value)
        
        return dirE
        
            
        
    
    
    
    ############     COMPACTING    ###########
    
    def isEnoughSpaceForProgram(self, program):
        
        space = len(program)
        returning = False
        for block in self.getUnusedBlocks():
            
            if block.blockSize() >= space:
                
                returning = True
                
        return returning
    
    
    def compact(self):
        
        self.sortBlocks(self.getUnusedBlocks(), lambda block: block.getStart())
        first = self.unusedBlocks[0]
        
        while self.areBlocksToCompact(first):
            
            used = self.getBlockToCompact(first)
            self.swapBlocks(first, used)
            self.checkForContinuous(first, self.unusedBlocks)
        
    
    
    def swapBlocks(self, block, used):
        
        us = block.getStart()
        ue = us + used.blockSize() - 1
        bs = ue + 1
        be = used.getEnd()
        
        oldStart = used.getStart()
        newStart = block.getStart
        
        for a in self.assignationTable:
            
            if a.getBaseDir() == oldStart:
                
                a.setBaseDir(newStart)
        
        block.setStart(bs)
        block.setEnd(be)
        used.setStart(us)
        used.setEnd(ue)
        
    
    def areBlocksToCompact(self, block):
        
        for b in self.usedBlocks:
            
            if b.getStart() > block.getStart():
                
                return True
            
        return False
        
    def getBlockToCompact(self, block):
        
        for b in self.usedBlocks:
            
            if b.getStart() > block.getStart():
                
                return b 
            
        return None
    
    
    def isEnoughSpaceForProgramCompacting(self, program):
        
        size = 0
        for b in self.unusedBlocks:
            
            size += b.blockSize()
            
            
        return size >= len(program)
    
    
    
if __name__ == '__main__':
    
    
    
    def printeable(cpu, str = "Higher"):
        
        print(str)
        
    def printeable2(cpu):
        
        print("Order")
        
        
    def defineVariable1(cpu):
        
        cpu.setVariable("x", 5)
        print("X variable setted")
        
    def defineVariable2(cpu):
        
        cpu.setVariable("y", 6)
        print("Y variable setted")
        
        
    def getVariableResult(cpu):
        
        x = cpu.getVariable("z")
        print(x)
        
    def sumVariables(cpu):
        
        x = cpu.getVariable("x")
        y = cpu.getVariable("y")
        cpu.setVariable("z", x + y)
        print(cpu.getVariable("z"))
    
    #io = IOInstruction(3)
    cpuvarx = CPUInstruction(printeable, 10)
    cpuvary = CPUInstruction(printeable2, 2)
    cpuvarz = CPUInstruction(printeable, 4)
    cpuvarw = CPUInstruction(printeable, 10)
    cpuvarz = CPUInstruction(printeable2, 5)
    cpusumvar = CPUInstruction(sumVariables)
    cpuresult = CPUInstruction(getVariableResult)
    
    settingvariables = Program([cpuvarx, cpuvary],1)
    settingvariables2 = Program([cpuvarw, cpuvarz],2)
    memo = Memory(512)
    adj = FirstAdjustment()
    assig = Assignation(adj, memo)
    block = Block(512, 600)
    block2 = Block(601, 700)
    block3 = Block(120, 511)
    block4 = Block(801, 900)
    listOfBlocks = [block2, block3, block4]
    #assig.assignation.allocateP(settingvariables.fullCode(), assig)
    #assig.assignation.allocateP(settingvariables2.fullCode(), assig)
    #print(assig.unusedBlocks[0].blockSize())
    #print(assig.usedBlocks[0].blockSize())
    appendedList = assig.checkForContinuous(block, listOfBlocks)
    print(appendedList[0].getStart())
    print(appendedList[0].getEnd())
    print(appendedList[1].getStart())
    print(appendedList[1].getEnd())
    print(appendedList[2].getStart())
    print(appendedList[2].getEnd())
    
    #print(assig.isContinuous(block, assig.unusedBlocks))
    #print(assig.appendBlocks(block, assig.unusedBlocks))
    #print(block.imFirst(block2))
    #print(block.reSize(block2))
    #print(block.continuousBlocks(block2))